﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CodeGenerator.CSharp;
using Modifier = CodeGenerator.CSharp.Modifiers;

namespace CodeAngel.Domain
{
    /// <summary>
    /// 
    /// </summary>
    public static class AggregateExtensionsFileGenerator
    {
        #region CreateAggregateExtensionsFileAsync

        /// <summary>
        /// Creates and writes to disk the aggregate extension file for the specified Domain.
        /// If this file already exists, it will be overwritten.
        /// </summary>
        public static Task CreateAggregateExtensionsFileAsync(this Domain thisDomain)
        {
            return Task.Factory.StartNew(() =>
            {
                AggregateExtensionsFileGenerator.WriteToDisk(thisDomain);
            });
        }

        #endregion

        #region WriteToDisk

        /// <summary>
        /// Creates and writes to disk the DtoExtensions.cs file. If this file already exists, 
        /// it will be overwritten.
        /// </summary>
        private static void WriteToDisk(Domain thisDomain)
        {
            var ThisFB = new FileBuilder();
            ThisFB.IsAutoGenerated = true;
            ThisFB.ProjectName = thisDomain.ServicesProjectName;
            ThisFB.Directory = thisDomain.ServicesExtensionsPath;
            ThisFB.FileName = "AggregateExtensions.cs";
            ThisFB.Namespace = thisDomain.ServicesNamespace;

            // Add the using namespaces for this file.
            ThisFB.UsingNamespaces.Add("System");
            ThisFB.UsingNamespaces.Add("System.Collections.Generic");
            ThisFB.UsingNamespaces.Add("System.Data");
            ThisFB.UsingNamespaces.Add("System.Data.SqlClient");
            ThisFB.UsingNamespaces.Add("System.Linq");
            ThisFB.UsingNamespaces.Add("System.Runtime.Serialization");
            ThisFB.UsingNamespaces.Add(thisDomain.DomainNamespace);
            ThisFB.UsingNamespaces.Add(thisDomain.FrameworkNamespace);
            ThisFB.UsingNamespaces.Add(thisDomain.AggregatesNamespace);


            // Add the DTO class.
            ThisFB.CodeUnits.Add(new DtoExtensionsClassBuilder(thisDomain));

            // Write this file to disk.
            ThisFB.WriteToDisk(OverwriteExistingFile.Yes);
        }

        #endregion

        #region DtoExtensionsClassBuilder

        /// <summary>
        /// Builds the DTOExtensions class - a collection of extension methods
        /// for DTO classes that need access to the full-CLR.
        /// </summary>
        private class DtoExtensionsClassBuilder : ClassBuilder
        {
            #region Constructors

            public DtoExtensionsClassBuilder(Domain thisDomain)
                : base("AggregateExtensions", Modifier.PublicStatic)
            {

                this.AddGetDtoHostProcedure(thisDomain);
                this.AddGetUpdateableDtosProcedure(thisDomain);
            }

            #endregion

            #region AddGetDtoHostProcedure

            /// <summary>
            /// 
            /// </summary>
            private void AddGetDtoHostProcedure(Domain thisDomain)
            {
                var ThisRegion = this.AddRegion("GetDtoHost");
                var ThisProcedure = ThisRegion.AddProcedure("GetDtoHost", Modifier.PublicStatic, "DtoHost");
                ThisProcedure.Documentation.Summary = "Gets a DtoHost object with the data that matches the identified filter suitable to this AggregateDef.  This method will run on the server.";
                ThisProcedure.Parameters.Add("thisAggregate", "Aggregate").IsExtensionParameter = true;
                ThisProcedure.Parameters.Add("filter", "string");
                ThisProcedure.Parameters.Add("userName", "string");
                ThisProcedure.Parameters.Add("password", "string");

                bool OnFirstAggregateDef = true;
                foreach (var ThisED in thisDomain.EntityDefs)
                {
                    foreach (var ThisAD in ThisED.AggregateDefs)
                    {
                        if (OnFirstAggregateDef)
                        {
                            OnFirstAggregateDef = false;
                            ThisProcedure.Body.Add("if (thisAggregate is {0})", ThisAD.ClassName);
                        }
                        else
                        {
                            ThisProcedure.Body.Add("else if (thisAggregate is {0})", ThisAD.ClassName);
                        }

                        ThisProcedure.Body.Add("{");
                        ThisProcedure.Body.Add("    var ThisCommand = DtoExtensions.GetStoredProcedureCommand(\"{0}\", filter, userName, password);", ThisAD.StoredProcedureName);
                        ThisProcedure.Body.Add("    try");
                        ThisProcedure.Body.Add("    {");
                        ThisProcedure.Body.Add("        ThisCommand.Connection.Open();");
                        ThisProcedure.Body.Add("        var ThisReader = ThisCommand.ExecuteReader(CommandBehavior.CloseConnection);");
                        ThisProcedure.Body.Add("        DtoHost ThisDtoHost = new DtoHost();");
                        ThisProcedure.Body.Add("        ThisReader.{0}(ThisDtoHost.Root{1});", ThisAD.EntityDef.ProcedureNames.CreateDtosFromDataReader(), ThisAD.EntityDef.Name.ToPlural());

                        foreach (var ChildED in ThisAD.GetUniqueEntities().OrderBy(i => i.Name))
                        {
                            ThisProcedure.Body.Add("        ThisReader.NextResult();");
                            ThisProcedure.Body.Add("        ThisReader.{0}(ThisDtoHost.{1});", ChildED.ProcedureNames.CreateDtosFromDataReader(), ChildED.Name.ToPlural());
                        }

                        ThisProcedure.Body.AddBlankLine();
                        ThisProcedure.Body.Add("        return ThisDtoHost;");
                        ThisProcedure.Body.Add("    }");
                        ThisProcedure.Body.Add("    catch (Exception ex)");
                        ThisProcedure.Body.Add("    {");
                        ThisProcedure.Body.Add("        throw ex;");
                        ThisProcedure.Body.Add("    }");
                        ThisProcedure.Body.Add("    finally");
                        ThisProcedure.Body.Add("    {");
                        ThisProcedure.Body.Add("        ThisCommand.Connection.Close();");
                        ThisProcedure.Body.Add("    }");

                        ThisProcedure.Body.Add("}");

                    }

                }
                ThisProcedure.Body.Add("else");
                ThisProcedure.Body.Add("    throw new Exception(\"Unable to determine the aggregate type needed to get DtoHost.  Aggregate Type: \" + thisAggregate.GetType().Name);");


            }

            #endregion

            #region AddGetUpdateableDtosProcedure

            /// <summary>
            /// 
            /// </summary>
            private void AddGetUpdateableDtosProcedure(Domain thisDomain)
            {
                Region ThisRegion = this.AddRegion("GetUpdateableDtos");
                var ThisProcedure = ThisRegion.AddProcedure("GetUpdateableDtos", Modifier.PublicStatic, "IEnumerable<IEnumerable<IDto>>");
                ThisProcedure.Documentation.Summary = "Gets a collection of a collection of dtos that are updateable.";
                ThisProcedure.Parameters.Add("thisAggregate", "Aggregate").IsExtensionParameter = true;

                bool OnFirstAggregateDef = true;
                foreach (var ThisED in thisDomain.EntityDefs)
                {
                    foreach (var ThisAD in ThisED.AggregateDefs)
                    {
                        if (OnFirstAggregateDef)
                        {
                            OnFirstAggregateDef = false;
                            ThisProcedure.Body.Add("if (thisAggregate is {0})", ThisAD.ClassName);
                        }
                        else
                        {
                            ThisProcedure.Body.Add("else if (thisAggregate is {0})", ThisAD.ClassName);
                        }

                        ThisProcedure.Body.Add("{");
                        ThisProcedure.Body.Add("var UpdateableDtos = new List<IEnumerable<IDto>>();");

                        if (ThisAD.DataElement.IsUpdateable)
                        {
                            ThisProcedure.Body.Add("UpdateableDtos.Add(thisAggregate.DtoHost.Root{0}.ToList<IDto>());", ThisAD.EntityDef.Name.ToPlural());
                        }
                        foreach (var ChildED in ThisAD.DataElement.DataElements.GetUpdateableEntityDefs())
                        {
                            ThisProcedure.Body.Add("UpdateableDtos.Add(thisAggregate.DtoHost.{0}.ToList<IDto>());", ChildED.Name.ToPlural());
                        }
                        ThisProcedure.Body.Add("return UpdateableDtos;");

                        ThisProcedure.Body.Add("}");

                    }
                }

                ThisProcedure.Body.Add("else");
                ThisProcedure.Body.Add("    throw new Exception(\"Unable to determine the aggregate type needed to get updateable DTOs.  Aggregate Type: \" + thisAggregate.GetType().Name);");
            }

            #endregion

        }

        #endregion

    }
}
